#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using JigLibX.Collision;
using JigLibX.Physics;
using JigLibX.Geometry;
using JigLibX.Math;
using Microsoft.Xna.Framework.Graphics;
using JigLibX.Utils;
using SqEngine.Components;
using sqengine.Components;
using sqengine.Engine;
using sqengine.Engine.Common;
#endregion

namespace sqengine.PhysicObjects
{

    /// <summary>
    /// Helps to combine the physics with the graphics.
    /// </summary>
    public abstract class PhysicObject : DrawableGameComponent
    {

        protected Body body;
        protected CollisionSkin collision;
        private VertexPositionColor[] wf;

        protected Model model;
        protected Vector3 color;
        private string _name;
        public bool CustomEffect = false;
        protected Vector3 scale = Vector3.One;
        public Texture2D _texture;
        
        public Body PhysicsBody { get { return body; } }
        public CollisionSkin PhysicsSkin { get { return collision; } }
        public string Name { get { return _name; } set { _name = value; } }

        // delegate declaration
        public delegate void ActionHandler(object sender, ActionArg ca);
        public event ActionHandler OnAction;

        protected static Random random = new Random();

        Camera _camera;

        Texture2D day_texture, night_texture, sunset_texture;
        Effect _sky_effect;
        SkyDome _skydome;
        AdvancedContent _adcontent;

        public PhysicObject(Game game, Model model)
            : base(game)
        {
            this.model = model;
            color = new Vector3(random.Next(255), random.Next(255), random.Next(255));
            color /= 255.0f;

            _camera = ((sqengine)game).Camera;
            _adcontent = ((sqengine)game).AdContent;
        }

        public PhysicObject(Game game)
            : base(game)
        {
            this.model = null;
            color = new Vector3(random.Next(255), random.Next(255), random.Next(255));
            color /= 255.0f;
        }

        public void SetSkydome(SkyDome skydome, Texture2D day, Texture2D sunset, Texture2D night, Effect sky_effect)
        {
            _skydome = skydome;
            day_texture = day;
            sunset_texture = sunset;
            night_texture = night;
            _sky_effect = sky_effect;
            CustomEffect = true;
            _sky_effect.CurrentTechnique = _sky_effect.Techniques["SkyDome"];

            RemapModel(model, _sky_effect);
        }

        protected Vector3 SetMass(float mass)
        {
            PrimitiveProperties primitiveProperties =
                new PrimitiveProperties(PrimitiveProperties.MassDistributionEnum.Solid, PrimitiveProperties.MassTypeEnum.Density, mass);

            float junk; Vector3 com; Matrix it, itCoM;

            collision.GetMassProperties(primitiveProperties, out junk, out com, out it, out itCoM);
            body.BodyInertia = itCoM;
            body.Mass = junk;

            return com;
        }

        Matrix[] boneTransforms = null;
        int boneCount = 0;


        public static void RemapModel(Model model, Effect effect)
        {
            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (ModelMeshPart part in mesh.MeshParts)
                {
                    part.Effect = effect;
                }
            }
        }


        public void Action(PlayerStats player)
        {
            if (OnAction != null)
            {
                OnAction(this, new ActionArg(player));
            }
        }


        public abstract void ApplyEffects(BasicEffect effect);

        public override void Draw(GameTime gameTime)
        {

            if (!CustomEffect)
            {
                DrawDefaultEffect(gameTime);
            }
            else
            {
                DrawTerrainEffect(gameTime);
            }


            if (((sqengine)this.Game).DebugDrawer.Enabled)
            {

                if (collision != null)
                {
                    wf = collision.GetLocalSkinWireframe();

                    // if the collision skin was also added to the body
                    // we have to transform the skin wireframe to the body space
                    if (body.CollisionSkin != null)
                    {
                        body.TransformWireframe(wf);
                    }

                    ((sqengine)this.Game).DebugDrawer.DrawShape(wf);
                }
            }


            base.Draw(gameTime);
        }

        private void DrawDefaultEffect(GameTime gameTime)
        {
            if (model != null)
            {
                if (boneTransforms == null || boneCount != model.Bones.Count)
                {
                    boneTransforms = new Matrix[model.Bones.Count];
                    boneCount = model.Bones.Count;
                }

                model.CopyAbsoluteBoneTransformsTo(boneTransforms);
                float depthBias = GraphicsDevice.RenderState.DepthBias;


                Camera camera = ((sqengine)this.Game).Camera;

                foreach (ModelMesh mesh in model.Meshes)
                {

                    foreach (BasicEffect effect in mesh.Effects)
                    {
                        // the body has an orientation but also the primitives in the collision skin
                        // owned by the body can be rotated!
                        if (body != null)
                        {
                            if (body.CollisionSkin != null)
                            {
                                effect.World = boneTransforms[mesh.ParentBone.Index] * Matrix.CreateScale(scale) * body.CollisionSkin.GetPrimitiveLocal(0).Transform.Orientation * body.Orientation * Matrix.CreateTranslation(body.Position);
                            }
                            else
                            {
                                effect.World = boneTransforms[mesh.ParentBone.Index] * Matrix.CreateScale(scale) * body.Orientation * Matrix.CreateTranslation(body.Position);
                            }
                        }
                        effect.View = camera.View;
                        effect.Projection = camera.Projection;

                        ApplyEffects(effect);
                        effect.Alpha = 1.0f;

                        effect.EnableDefaultLighting();
                        effect.PreferPerPixelLighting = true;


                    }

                    mesh.Draw();
                }


            }
        }
        private void DrawTerrainEffect(GameTime gameTime)
        {
            Matrix View = _camera.View;
            Matrix Projection = _camera.Projection;
            RemapModel(model, _sky_effect);

            foreach (ModelMesh mesh in model.Meshes)
            {
                //Matrix World = body.Orientation;

                Matrix WrlMatrix = Matrix.Identity;
                if (body.CollisionSkin != null)
                {
                    WrlMatrix = boneTransforms[mesh.ParentBone.Index] * Matrix.CreateScale(scale) * body.CollisionSkin.GetPrimitiveLocal(0).Transform.Orientation * body.Orientation * Matrix.CreateTranslation(body.Position);
                }
                else
                {
                    WrlMatrix = boneTransforms[mesh.ParentBone.Index] * Matrix.CreateScale(scale) * body.Orientation * Matrix.CreateTranslation(body.Position);
                }

                Matrix World = body.Orientation;
                Matrix WorldIT = Matrix.Invert(WrlMatrix);
                WorldIT = Matrix.Transpose(WorldIT);

                if (mesh.Effects.Count > 0)
                {
                    foreach (Effect effect in mesh.Effects)
                    {


                        effect.Parameters["WorldIT"].SetValue(WorldIT);
                        effect.Parameters["WorldViewProj"].SetValue(WrlMatrix * View * Projection);
                        effect.Parameters["ViewInv"].SetValue(Matrix.Invert(View));
                        effect.Parameters["World"].SetValue(WrlMatrix);

                        if (_texture != null)
                            effect.Parameters["DiffuseTexture"].SetValue(_texture);

                        effect.Parameters["SkyTextureNight"].SetValue(night_texture);
                        effect.Parameters["SkyTextureSunset"].SetValue(sunset_texture);
                        effect.Parameters["SkyTextureDay"].SetValue(day_texture);

                        effect.Parameters["isSkydome"].SetValue(false);

                        effect.Parameters["LightDirection"].SetValue(-_skydome.Parameters.LightDirection);
                        effect.Parameters["LightColor"].SetValue(_skydome.Parameters.LightColor);
                        effect.Parameters["LightColorAmbient"].SetValue(_skydome.Parameters.LightColorAmbient);
                        effect.Parameters["FogColor"].SetValue(_skydome.Parameters.FogColor);
                        effect.Parameters["fDensity"].SetValue(0.0003f);
                        effect.Parameters["SunLightness"].SetValue(_skydome.Parameters.SunLightness);
                        effect.Parameters["sunRadiusAttenuation"].SetValue(_skydome.Parameters.SunRadiusAttenuation);
                        effect.Parameters["largeSunLightness"].SetValue(_skydome.Parameters.LargeSunLightness);
                        effect.Parameters["largeSunRadiusAttenuation"].SetValue(_skydome.Parameters.LargeSunRadiusAttenuation);
                        effect.Parameters["dayToSunsetSharpness"].SetValue(_skydome.Parameters.DayToSunsetSharpness);
                        effect.Parameters["hazeTopAltitude"].SetValue(_skydome.Parameters.HazeTopAltitude);

                    }
                    mesh.Draw();
                }
            }
        }

    }




}
